0282. 大型项目的类型组织
1. 🎯 本节内容
- 类型文件组织
- 模块边界管理
- 共享类型库
- 类型版本管理
- 最佳实践
2. 🫧 评价
大型项目需要清晰的类型组织策略。
- 按功能域划分类型
- 避免循环依赖
- 使用命名空间隔离
- 共享类型独立包管理
- 版本化类型定义
3. 🤔 类型文件组织?
清晰的文件结构便于维护。
3.1. 按功能域划分
src/
├── types/
│ ├── index.ts # 导出所有类型
│ ├── common/ # 通用类型
│ │ ├── index.ts
│ │ ├── http.ts # HTTP 相关
│ │ ├── errors.ts # 错误类型
│ │ └── utils.ts # 工具类型
│ ├── user/ # 用户域
│ │ ├── index.ts
│ │ ├── models.ts # 用户模型
│ │ ├── api.ts # API 类型
│ │ └── events.ts # 事件类型
│ └── product/ # 产品域
│ ├── index.ts
│ ├── models.ts
│ └── api.ts
├── modules/
│ ├── user/
│ │ ├── UserService.ts
│ │ └── UserRepository.ts
│ └── product/
│ ├── ProductService.ts
│ └── ProductRepository.ts1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
3.2. 类型文件示例
ts
// types/common/http.ts
export interface ApiResponse<T> {
data: T
status: number
message?: string
}
export interface PaginationParams {
page: number
pageSize: number
}
export interface PaginatedResponse<T> {
items: T[]
total: number
page: number
pageSize: number
}
// types/common/errors.ts
export enum ErrorCode {
ValidationError = 'VALIDATION_ERROR',
NotFound = 'NOT_FOUND',
Unauthorized = 'UNAUTHORIZED',
}
export interface AppError {
code: ErrorCode
message: string
details?: unknown
}
// types/user/models.ts
export interface User {
id: string
email: string
name: string
role: UserRole
createdAt: Date
}
export enum UserRole {
Admin = 'admin',
User = 'user',
Guest = 'guest',
}
// types/user/api.ts
import type {
ApiResponse,
PaginatedResponse,
PaginationParams,
} from '../common'
import type { User } from './models'
export interface CreateUserRequest {
email: string
name: string
password: string
}
export type CreateUserResponse = ApiResponse<User>
export interface ListUsersRequest extends PaginationParams {
role?: UserRole
}
export type ListUsersResponse = PaginatedResponse<User>
// types/index.ts
export * from './common'
export * from './user'
export * from './product'1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
3.3. 使用路径别名
json
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/types/*": ["src/types/*"],
"@/modules/*": ["src/modules/*"],
"@/utils/*": ["src/utils/*"]
}
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
ts
// ✅ 清晰的导入路径
import type { User, CreateUserRequest } from '@/types/user'
import type { ApiResponse } from '@/types/common'
// ❌ 相对路径复杂
import type { User } from '../../../types/user/models'1
2
3
4
5
6
2
3
4
5
6
4. 🤔 模块边界管理?
明确的模块边界避免耦合。
4.1. 定义公共接口
ts
// modules/user/public.ts
export interface IUserService {
createUser(data: CreateUserRequest): Promise<User>
getUser(id: string): Promise<User | null>
deleteUser(id: string): Promise<void>
}
// modules/user/UserService.ts
import type { IUserService } from './public'
class UserService implements IUserService {
// 实现细节
}
export default UserService
// ❌ 外部不应直接访问内部实现
// import UserService from "@/modules/user/UserService";
// ✅ 通过公共接口访问
import type { IUserService } from '@/modules/user/public'1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
4.2. 避免循环依赖
ts
// ❌ 循环依赖
// user/types.ts
import { Product } from '../product/types'
export interface User {
id: string
favoriteProducts: Product[] // 依赖 Product
}
// product/types.ts
import { User } from '../user/types'
export interface Product {
id: string
creator: User // 依赖 User
}
// ✅ 提取共享类型
// types/common/ids.ts
export type UserId = string & { __brand: 'UserId' }
export type ProductId = string & { __brand: 'ProductId' }
// user/types.ts
import type { UserId, ProductId } from '@/types/common/ids'
export interface User {
id: UserId
favoriteProducts: ProductId[] // 只依赖 ID
}
// product/types.ts
import type { UserId, ProductId } from '@/types/common/ids'
export interface Product {
id: ProductId
creatorId: UserId // 只依赖 ID
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
4.3. 使用依赖注入
ts
// ✅ 接口定义
export interface ILogger {
log(message: string): void
error(message: string, error: Error): void
}
export interface IDatabase {
query<T>(sql: string): Promise<T[]>
}
// ✅ 依赖注入
class UserService {
constructor(private logger: ILogger, private db: IDatabase) {}
async getUser(id: string) {
this.logger.log(`Fetching user ${id}`)
const users = await this.db.query<User>(`SELECT * FROM users WHERE id = ?`)
return users[0]
}
}
// ✅ 创建容器
interface Container {
logger: ILogger
database: IDatabase
userService: UserService
}
function createContainer(): Container {
const logger = new ConsoleLogger()
const database = new PostgresDatabase()
const userService = new UserService(logger, database)
return { logger, database, userService }
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
5. 🤔 共享类型库?
独立的类型包供多个项目使用。
5.1. 创建类型包
@company/types/
├── package.json
├── tsconfig.json
├── src/
│ ├── index.ts
│ ├── common.ts
│ ├── user.ts
│ └── product.ts
└── dist/
├── index.d.ts
└── ...1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
json
// package.json
{
"name": "@company/types",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist"],
"scripts": {
"build": "tsc",
"prepublishOnly": "npm run build"
}
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
json
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"declaration": true, // 生成 .d.ts
"declarationMap": true, // 生成 .d.ts.map
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"]
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
5.2. 版本化发布
ts
// src/user.ts
/**
* User model
* @since 1.0.0
*/
export interface User {
id: string
email: string
name: string
/**
* @since 1.1.0
*/
avatar?: string
/**
* @deprecated Use email instead
* @since 1.0.0
*/
username?: string
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
5.3. 在项目中使用
json
// 项目 package.json
{
"dependencies": {
"@company/types": "^1.1.0"
}
}1
2
3
4
5
6
2
3
4
5
6
ts
// 项目中使用
import type { User, Product } from '@company/types'
function getUser(): User {
return {
id: '1',
email: 'user@example.com',
name: 'John Doe',
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
6. 🤔 类型版本管理?
管理类型定义的演进。
6.1. 向后兼容的更改
ts
// ✅ 添加可选属性
export interface User {
id: string
email: string
name: string
avatar?: string // 新增,不破坏现有代码
}
// ✅ 扩展联合类型
export type UserRole = 'admin' | 'user' | 'guest' | 'moderator' // 新增 moderator
// ✅ 使用泛型增加灵活性
export interface ApiResponse<T = unknown> {
// 添加默认类型
data: T
status: number
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
6.2. 破坏性更改
ts
// ❌ 删除属性(破坏性)
export interface User {
id: string
// email: string; // 删除会破坏现有代码
}
// ✅ 先标记为废弃
export interface User {
id: string
/**
* @deprecated Use emailAddress instead
*/
email?: string
emailAddress: string
}
// ✅ 下个主版本再删除1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
6.3. 使用版本命名空间
ts
// types/v1/user.ts
export namespace V1 {
export interface User {
id: string
username: string
}
}
// types/v2/user.ts
export namespace V2 {
export interface User {
id: string
email: string // 改用 email
name: string
}
}
// 使用
import { V1, V2 } from '@/types'
function migrateUser(oldUser: V1.User): V2.User {
return {
id: oldUser.id,
email: `${oldUser.username}@example.com`,
name: oldUser.username,
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
7. 🤔 最佳实践?
大型项目的类型管理建议。
7.1. 文档化类型
ts
/**
* 用户模型
*
* @example
* ```ts
* const user: User = {
* id: "123",
* email: "user@example.com",
* name: "John Doe",
* role: UserRole.User
* };
* ```
*/
export interface User {
/** 唯一标识符 */
id: string
/** 邮箱地址,用于登录 */
email: string
/** 显示名称 */
name: string
/** 用户角色,决定权限 */
role: UserRole
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
7.2. 使用工具类型
ts
// ✅ 提取公共模式
type WithTimestamps = {
createdAt: Date
updatedAt: Date
}
export interface User extends WithTimestamps {
id: string
email: string
}
export interface Product extends WithTimestamps {
id: string
name: string
}
// ✅ 创建派生类型
export type CreateUserDTO = Omit<User, 'id' | 'createdAt' | 'updatedAt'>
export type UpdateUserDTO = Partial<CreateUserDTO>
export type UserResponse = Pick<User, 'id' | 'email' | 'name'>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
7.3. 类型测试
ts
// types/__tests__/user.test.ts
import type { User, CreateUserDTO } from '../user'
// ✅ 编译时类型测试
type AssertEqual<T, U> = T extends U ? (U extends T ? true : false) : false
// 测试:CreateUserDTO 应该不包含 id
type Test1 = AssertEqual<keyof CreateUserDTO, 'email' | 'name'>
// 如果类型不匹配,会导致编译错误
const test1: Test1 = true
// ✅ 运行时类型验证
import { z } from 'zod'
const UserSchema = z.object({
id: z.string(),
email: z.string().email(),
name: z.string().min(1),
})
// 从 schema 推导类型,保持同步
type UserFromSchema = z.infer<typeof UserSchema>
// 验证类型兼容性
const testUser: User = {} as UserFromSchema // 应该兼容1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26